/* packet-xapian.c
 * Xapian Remote Database packet disassembly
 * More details can be found at
 * 
 * Chapter 4 ( Call Content Connection Interface )
 *
 * $Id: packet-xapian.c 18197 2006-05-21 05:12:17Z sahlberg $
 *
 * Copyright (c) 2007 by Sidnei da Silva <sidnei@enfoldsystems.com>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1999 Gerald Combs
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

/* Include files */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
#include <string.h>

#include <glib.h>

#include <epan/packet.h>
#include <epan/prefs.h>
#include <epan/strutil.h>
#include <epan/addr_resolv.h>
#include <epan/emem.h>
#include <epan/conversation.h>
#include <epan/dissectors/packet-tcp.h>

/* Define tcp_port for xapian intercept */

#define TCP_PORT_XAPIAN 9103
#define FRAME_HEADER_LEN 2

void proto_reg_handoff_xapian(void);

/* Define the xapian proto */

static int proto_xapian = -1;

/* Header definitions */

static int hf_xapian_msg_type = -1;
static int hf_xapian_msg_length = -1;
static int hf_xapian_msg_data = -1;

/* Define the tree for xapian */

static int ett_xapian = -1;

/* Define the message type -> name mappings */

/// Message types (client -> server).
enum message_type {
  MSG_ALLTERMS,		// All Terms
  MSG_COLLFREQ,		// Get Collection Frequency
  MSG_DOCUMENT,		// Get Document
  MSG_TERMEXISTS,		// Term Exists?
  MSG_TERMFREQ,		// Get Term Frequency
  MSG_KEEPALIVE,		// Keep-alive
  MSG_DOCLENGTH,		// Get Doc Length
  MSG_QUERY,			// Run Query
  MSG_TERMLIST,		// Get TermList
  MSG_POSITIONLIST,		// Get PositionList
  MSG_REOPEN,			// Reopen
  MSG_UPDATE,			// Get Updated DocCount and AvLength
  MSG_ADDDOCUMENT,		// Add Document
  MSG_CANCEL,			// Cancel
  MSG_DELETEDOCUMENT,		// Delete Document
  MSG_FLUSH,			// Flush
  MSG_REPLACEDOCUMENT,	// Replace Document
  MSG_GETMSET,		// Get MSet
  MSG_SHUTDOWN,		// Shutdown
  MSG_MAX
};

/// Reply types (server -> client).
enum reply_type {
  REPLY_GREETING,		// Greeting
  REPLY_EXCEPTION,		// Exception
  REPLY_DONE,			// Done sending list
  REPLY_ALLTERMS,		// All Terms
  REPLY_COLLFREQ,		// Get Collection Frequency
  REPLY_DOCDATA,		// Get Document
  REPLY_TERMDOESNTEXIST,	// Term Doesn't Exist
  REPLY_TERMEXISTS,		// Term Exists
  REPLY_TERMFREQ,		// Get Term Frequency
  REPLY_DOCLENGTH,		// Get Doc Length
  REPLY_RESULTS,		// Results (MSet)
  REPLY_STATS,		// Stats
  REPLY_TERMLIST,		// Get Termlist
  REPLY_POSITIONLIST,		// Get PositionList
  REPLY_UPDATE,		// Get Updated DocCount and AvLength
  REPLY_VALUE,		// Document Value
  REPLY_ADDDOCUMENT,		// Add Document
  REPLY_MAX
};

static const value_string msg_type_names[] = {
  { MSG_ALLTERMS, "All Terms" },
  { MSG_COLLFREQ, "Get Collection Frequency" },
  { MSG_DOCUMENT, "Get Document" },
  { MSG_TERMEXISTS, "Term Exists?" },
  { MSG_TERMFREQ, "Get Term Frequency" },
  { MSG_KEEPALIVE, "Keep-Alive" },
  { MSG_DOCLENGTH, "Get Doc Length" },
  { MSG_QUERY, "Run Query" },
  { MSG_TERMLIST, "Get TermList" },
  { MSG_POSITIONLIST, "Get PositionList" },
  { MSG_REOPEN, "Reopen" },
  { MSG_UPDATE, "Update" },
  { MSG_ADDDOCUMENT, "Add Document" },
  { MSG_CANCEL, "Cancel" },
  { MSG_DELETEDOCUMENT, "Delete Document" },
  { MSG_FLUSH, "Flush" },
  { MSG_REPLACEDOCUMENT, "Replace Document" },
  { MSG_GETMSET, "Get MSet" },
  { MSG_MAX, "" }
};

static const value_string reply_type_names[] = {
  { REPLY_GREETING, "Greeting" },
  { REPLY_EXCEPTION, "Exception" },
  { REPLY_DONE, "Done sending list" },
  { REPLY_ALLTERMS, "All Terms" },
  { REPLY_COLLFREQ, "Get Collection Frequency" },
  { REPLY_DOCDATA, "Get Document" },
  { REPLY_TERMDOESNTEXIST, "Term Doesn't Exist" },
  { REPLY_TERMEXISTS, "Term Exists" },
  { REPLY_TERMFREQ, "Get Term Frequency" },
  { REPLY_DOCLENGTH, "Get Doc Length" },
  { REPLY_RESULTS, "Results (MSet)" },
  { REPLY_STATS, "Stats" },
  { REPLY_TERMLIST, "Get Termlist" },
  { REPLY_POSITIONLIST, "Get PositionList" },
  { REPLY_UPDATE, "Get Updated DocCount and AvLength" },
  { REPLY_VALUE, "Document Value" },
  { REPLY_ADDDOCUMENT, "Add Document" },
  { REPLY_MAX, "" }
};

/* 
 * Here are the global variables associated with the preferences
 * for xapian
 */

static guint global_tcp_port_xapian = TCP_PORT_XAPIAN;
static guint tcp_port_xapian = TCP_PORT_XAPIAN;

typedef struct xapian_conv_info_t {
  guint8 msg_type;
  guint  msg_len;
} xapian_conv_info_t;
static xapian_conv_info_t *xapian_info_items;

/* Determine PDU length for xapian protocol */
static guint
get_xapian_message_len(packet_info *pinfo, tvbuff_t *tvb, int offset){
  int i;
  int shift = 0;
  guint len;
  guint ch;

  len = tvb_get_guint8(tvb, offset+1);
  if (len != 0xff) {
    len += 2;
    return len;
  }
  
  len = 0;
  for (i=0; i<=4; i++){
    ch = tvb_get_guint8(tvb, offset+2+i);
    len |= ch & 0x7f << shift;
    shift += 7;
    if ((ch & 0x80) == 0)
      break;
  }
  len += 255 + i + 2;
  return len;
}

/* This method dissects fully reassembled messages */
static void 
dissect_xapian_msg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) {
  conversation_t *conversation;
  xapian_conv_info_t *xapian_info;
  gboolean is_reply = FALSE;
  guint length_remaining;
  guint8 *data;
  
  /*
   * Do we have a conversation for this connection?
   */
  conversation = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst,
				   pinfo->ptype, pinfo->srcport,
				   pinfo->destport, 0);
  if (conversation == NULL) {
    /* We don't yet have a conversation, so create one. */
    conversation = conversation_new(pinfo->fd->num, &pinfo->src, &pinfo->dst,
				    pinfo->ptype, pinfo->srcport,
				    pinfo->destport, 0);
  }

  /*
   * Do we already have a type and mechanism?
   */
  xapian_info = conversation_get_proto_data(conversation, proto_xapian);
  if (xapian_info == NULL) {
    /* No.  Attach that information to the conversation, and add
     * it to the list of information structures.
     */
    xapian_info = se_alloc(sizeof(xapian_conv_info_t));
    conversation_add_proto_data(conversation, proto_xapian, xapian_info);
  }

  xapian_info->msg_type = tvb_get_guint8(tvb, 0);
  xapian_info->msg_len = get_xapian_message_len(pinfo, tvb, 0);

  length_remaining = tvb_ensure_length_remaining(tvb, 2);
  data = tvb_get_string(tvb, 2, length_remaining);

  if (pinfo->srcport == tcp_port_xapian)
    is_reply = TRUE;

  /* Set the protocol column */
  if (check_col(pinfo->cinfo, COL_PROTOCOL)){
    col_add_str(pinfo->cinfo, COL_PROTOCOL, "XAPIAN");
  }

  /* Clear out the info column */
  if (check_col(pinfo->cinfo, COL_INFO)){
    col_clear(pinfo->cinfo, COL_INFO);
  }
  if (check_col(pinfo->cinfo, COL_INFO)) {
    if (is_reply)
      col_add_fstr(pinfo->cinfo, COL_INFO, "Type %s",
		   val_to_str(xapian_info->msg_type, 
			      reply_type_names, 
			      "Unknown (0x%02x)"));
    else
      col_add_fstr(pinfo->cinfo, COL_INFO, "Type %s",
		   val_to_str(xapian_info->msg_type, 
			      msg_type_names, 
			      "Unknown (0x%02x)"));
  }

  /* 
   *If we have a non-null tree (ie we are building the proto_tree
   * instead of just filling out the columns ), then add a XAPIAN
   * tree node.
   */
  if(tree) {
    proto_item *ti = NULL;
    proto_tree *xapian_tree = NULL;
    ti = proto_tree_add_item(tree, proto_xapian, tvb, 0, 
			     xapian_info->msg_len, FALSE);
    if (is_reply)
      proto_item_append_text(ti, ", Type %s", 
			     val_to_str(xapian_info->msg_type, 
					reply_type_names, 
					"Unknown (0x%02x)"));
    else
      proto_item_append_text(ti, ", Type %s", 
			     val_to_str(xapian_info->msg_type, 
					msg_type_names, 
					"Unknown (0x%02x)"));

    xapian_tree = proto_item_add_subtree(ti, ett_xapian);
    proto_tree_add_item(xapian_tree, hf_xapian_msg_length, tvb, 1, 1, FALSE);
    proto_tree_add_string(xapian_tree, hf_xapian_msg_data, tvb, 2, 
			  length_remaining, data);
  }

}

/* The main dissecting routine */
static void
dissect_xapian(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) {
  tcp_dissect_pdus(tvb, pinfo, tree, TRUE, FRAME_HEADER_LEN,
		   get_xapian_message_len, dissect_xapian_msg);
}

void 
proto_register_xapian(void) {
  static hf_register_info hf[] = {
    { &hf_xapian_msg_type,
      { "Message Type",
	"xapian.type",
	FT_UINT8, BASE_DEC,
	VALS(msg_type_names), 0x0,
	NULL, HFILL }
    },
    { &hf_xapian_msg_length,
      { "Message Length",
	"xapian.length",
	FT_UINT8, BASE_DEC,
	NULL, 0x0,
	NULL, HFILL }
    },
    { &hf_xapian_msg_data,
      { "Message Data",
	"xapian.data",
	FT_STRING, BASE_NONE,
	NULL, 0x0,
	NULL, HFILL }
    }
  };
  
  static gint *ett[] = {
    &ett_xapian
  };

  module_t *xapian_module;

  if (proto_xapian == -1){
    proto_xapian = proto_register_protocol("Xapian Remote Database",
					   "XAPIAN",
					   "xapian");
  }
  xapian_module = prefs_register_protocol(proto_xapian,
					  proto_reg_handoff_xapian);

  proto_register_field_array(proto_xapian, hf, array_length(hf));
  proto_register_subtree_array(ett, array_length(ett));

  prefs_register_uint_preference(xapian_module, "tcp_port",
				 "Xapian TCP Port",
				 "The TCP port on which "
				 "Xapian Remote Database "
				 "packets will be sent",
				 10, &global_tcp_port_xapian);
}

/* The registration hand-off routing */

void
proto_reg_handoff_xapian(void) {
  static int xapian_initialized = FALSE;
  static dissector_handle_t xapian_handle;

  if(!xapian_initialized) {
    xapian_handle = create_dissector_handle(dissect_xapian, proto_xapian);
    xapian_initialized = TRUE;
  } else {
    dissector_delete("tcp.port", tcp_port_xapian, xapian_handle);
  }

  tcp_port_xapian = global_tcp_port_xapian;

  dissector_add("tcp.port", global_tcp_port_xapian, xapian_handle);
}
